#include "stopwatch.h"
#include <time.h>
#include <stdexcept>


namespace evm {

void StopWatch::start() {
    if (started_)
        return;

    started_at_ = now();
    started_ = true;
    splits_.clear();
}

void StopWatch::stop() {
    if (!started_)
        return;

    stopped_at_ = now();
    started_ = false;
}

void StopWatch::restart() {
    if (started_)
        stop();
    start();
}

void StopWatch::resume() {
    if (!started_)
        started_ = true;
}

bool StopWatch::started() {
    return started_;
}

bool StopWatch::stopped() {
    return !started_;
}

long StopWatch::elapsed(long maximum) {
    long elapsed;
    if (!started_)  // has stopped
        elapsed = stopped_at_ - started_at_;
    else
        elapsed = now() - started_at_;
    if (maximum >= 0 && elapsed > maximum)
        elapsed = maximum;
    return elapsed > 0 ? elapsed : 0;
}

long StopWatch::leftover() {
    if (!started_)
        throw runtime_error("Can not get the leftover time of a stopwatch "
                            "that has not been started or has been stopped");
    if (duration_ < 0)
        return -1;  // means infinite time
    long leftover = duration_ - elapsed();
    return leftover > 0 ? leftover : 0;
}

bool StopWatch::expired() {
    if (duration_ < 0)
        return false;
    return elapsed() > duration_;
}

const StopWatch::Split& StopWatch::split() {
    if (!started_)
        throw runtime_error("Can not create a split time of a stopwatch "
                            "that has not been started or has been stopped");
    long elapsed_time = elapsed();
    long length;
    if (splits_.empty())
        length = elapsed_time;
    else {
        length = elapsed_time - splits_.back().first;
        length = length > 0 ? length : 0;
    }
    splits_.emplace_back(elapsed_time, length);
    return splits_.back();
}

const StopWatch::Splits &StopWatch::splits() {
    return splits_;
}

long StopWatch::now() {
    timespec ts;
    if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
        throw runtime_error("clock_gettime fail");
    return static_cast<long>(ts.tv_sec * 1e3 + ts.tv_nsec / 1e6);
}

}
